#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
#include "Util.hpp"
#include "log.hpp"

namespace ns_index
{
    // 正排结构体，文档id->文档内容
    struct DocInfo
    {
        uint64_t doc_id;
        std::string title;
        std::string content;
        std::string url;
    };

    // 倒排结构体，关键字->文档id
    struct InvertedElem
    {
        uint64_t doc_id;
        std::string word;
        int weight;
    };

    // 重命名倒排拉链
    typedef std::vector<InvertedElem> InvertedList;

    // 单例模式
    class Index
    {
    private:
        Index() = default;
        Index(const Index&) = delete;
        Index& operator=(const Index&) = delete;
    public:
        ~Index() = default;

        // 根据文档id查找对应的DocInfo, id从0开始
        DocInfo* GetForwardIndex(uint64_t file_id)
        {
            // 判断文档id合法性
            if(file_id >= forward_index.size())
            {
                return nullptr;
            }

            return &forward_index[file_id];
        }
        // 根据关键字查找对应倒排拉链
        InvertedList* GetInvertedList(const std::string& key)
        {
            auto iter = inverted_index.find(key);
            if(iter == inverted_index.end())
            {
                return nullptr;
            }
            return &iter->second;
        }
        // 建立索引
        bool BuildIndex(const std::string& text_path)
        {
            std::ifstream ifs(text_path, std::ios::in | std::ios::binary);
            if(!ifs.is_open())
            {
                std::cerr << "open " << text_path << " failed" << std::endl;
                return false;
            }

            size_t cnt = 0;
            std::string line;
            // 一次读取一行处理一行
            while(std::getline(ifs, line))
            {
                DocInfo* doc = BuildForwardIndex(line);
                if(doc == nullptr)
                {
                    std::cerr << "built forward index failed" << std::endl;
                    continue;
                }

                if(!BuildInvertedIndex(*doc))
                {
                    std::cerr << "built inverted index failed" << std::endl;
                    continue;
                }
                cnt++;
                if(cnt % 100 == 0)
                {
                    // std::cout << "已构建 " << cnt << " 个索引" << std::endl;
                    LOG(NORMAL, "已构建的索引数: " + std::to_string(cnt));
                }
            }

            return true;
        }

        static Index* GetInstance()
        {
            if(pinstance == nullptr)
            {
                mtx.lock();
                if(pinstance == nullptr)
                {
                    pinstance = new Index();
                }
                mtx.unlock();
            }
            return pinstance;
        }
    private:
        DocInfo* BuildForwardIndex(const std::string& line)
        {
            // 1. 切分字符串
            std::vector<std::string> tokens;
            const std::string sep = "\3";
            ns_util::StringUtil::SpiltString(line, &tokens, sep);

            if(tokens.size() != 3)
            {
                std::cerr << "when buildding forward index ,cutting string error" << std::endl;
                return nullptr;
            }
            // 2. 将切分的内容保存到DocInfo中
            DocInfo doc;
            doc.title = tokens[0];
            doc.content = tokens[1];
            doc.url = tokens[2];
            doc.doc_id = forward_index.size();

            // 3. 将DocInfo插入forward_index中
            forward_index.push_back(std::move(doc));

            return &forward_index.back();
        }

        bool BuildInvertedIndex(const DocInfo& doc)
        {
            // 1. 分词 2. 词频统计 3. 保存到倒排索引中
            struct WordCnt // 词频统计结构体：将标题中出现和正文中出现的词分开统计
            {
                int title_cnt;
                int content_cnt;
            };
            std::unordered_map<std::string, WordCnt> cnt_ump; // 统计一个词对应词频的map
            // 先对标题进行分词和词频统计
            std::vector<std::string> tokens_title;
            // ns_util::JiebaUtil::CutString(doc.title, &tokens_title);
            ns_util::JiebaUtil::GetInstance()->CutString(doc.title, &tokens_title);

            for(std::string word : tokens_title)
            {
                // 统计时全部词转换为小写，方便统计，这里是否可以直接用引用改变数组内容，减少拷贝，效率会不会好一点？TODO
                boost::to_lower(word);
                cnt_ump[word].title_cnt++;
            }

            // 再对正文进行分词和词频统计
            std::vector<std::string> tokens_content;
            // ns_util::JiebaUtil::CutString(doc.content, &tokens_content);
            ns_util::JiebaUtil::GetInstance()->CutString(doc.content, &tokens_content);

            for(std::string word : tokens_content)
            {
                boost::to_lower(word);
                cnt_ump[word].content_cnt++;
            }
            
            #define W1 10
            #define W2 1
            // 构建倒排拉链保存到倒排索引中
            for(auto& ump_pair : cnt_ump)
            {
                InvertedElem elem;
                elem.doc_id = doc.doc_id;
                elem.word = ump_pair.first;
                // 计算相关度，相关度算法可优化TODO
                elem.weight = ump_pair.second.title_cnt * W1 + ump_pair.second.content_cnt * W2;
                inverted_index[ump_pair.first].push_back(std::move(elem));
            }
            return true;
        }
    private:
        std::vector<DocInfo> forward_index; // 正排索引，数组下标就是文档id
        std::unordered_map<std::string, InvertedList> inverted_index; // 倒排索引，一个关键字对应一组InvertedElem

        static Index* pinstance;
        static std::mutex mtx;
    };
    // 在类外初始化对象
    Index* Index::pinstance = nullptr;
    std::mutex Index::mtx;
}